package oboe.controllers.jml.helpers;

import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.system.ApplicationDomain;
import flash.utils.Dictionary;

import oboe.controllers.jml.JMLController;
import oboe.controllers.jml.events.JMLEvent;
import oboe.core.Controller;

import flash.Vector;

class JMLNode extends Controller {
private var asset:DisplayObject;
private var parent:JMLController;
private var jmlfunctions:Dictionary; 
private var jmlconstants:Dictionary;

private static inline var stack:Vector<JMLNode> = new Vector();
	
public function new( head:String, jml:Object, parent:JMLController )
{
	var parts:Array = head.split(":");
	var type:String;
	var id:String;
	this.parent = parent;
	if( parts.length>0 ) type = parts[0];
	if( parts.length>1 ) id = parts[1];
	stack.push( this );
	
	super( createDisplayObject(type) );
	
	this.asset = this.getDisplayObject();

	if( id )
	{
	parent.newIdIndex( id, this );
	this.asset.name = id;
	}

	this.jmlfunctions = new Dictionary();
	this.addJMLFunctions( this.jmlfunctions );
	this.jmlconstants = new Dictionary();
	this.addJMLConstants( this.jmlconstants );
	
	var result:Object = {};
	
	if( parent.stylejml )
	{
	if( ("->" + type) in parent.stylejml ) this.processJML( parent.stylejml["->"+type], result );
	if( ("=>" + id) in parent.stylejml ) this.processJML( parent.stylejml["=>"+id], result );
	this.processJML( jml, result );
	if( ("<-" + type) in parent.stylejml ) this.processJML( parent.stylejml["<-"+type], result );
	if( ("<=" + id) in parent.stylejml ) this.processJML( parent.stylejml["<="+id], result );
	}
	else
	{
	this.processJML( jml, result );
	}
	this.asset.dispatchEvent( new JMLEvent("JMLComplete", result ) );
}

public function jmlRoot():JMLController
{
	return this.parent;
}

public function slot_setProperty( name:String, value:Object ):Void
{
	this.asset[name] = value;
}

public function getAsset():DisplayObject
{
	return this.asset;
}

public function getProperty( name:String ):Object
{
	return this.asset[name];
}

public function slot_getProperty( name:String, out:Array ):Void
{
	out.push( this.asset[name] );
}

public function slot_setScrollbarVisible( state:Bool ):Void
{
	if( this.asset is JMLClipper ) JMLClipper( this.asset ).setScrollbarVisible( state );
}

private function addJMLFunctions( dict:Dictionary ):Void
{
	for each( var node:JMLNode in stack )
	{
	for( var key:String in node.jmlfunctions )
	{
		dict[key] = node.jmlfunctions[key];
	}
	}
	dict["shortSignal"] = this.shortSignal;
	dict["signal"] = this.signal;
	dict["signalDown"] = this.signalDown;
	dict["signalUp"] = this.signalUp;
	dict["message"] = this.message;
	dict["root.message"] = function ( ...params ):Void
	{
	parent.message.apply( parent, params );
	}
	dict["set"] = function (k:String, v:Object):Void
	{ 
	var r:Array = resolve(k);
	r[0][ r[1] ] = v; 
	};
}

private function addJMLConstants( dict:Dictionary ):Void
{
	for each( var node:JMLNode in stack )
	{
	for( var key:String in node.jmlconstants )
	{
		dict[key] = node.jmlconstants[key];
	}
	}
}

private function createDisplayObject( type:String ):DisplayObject
{
	return parent.getDisplayObjectForType( type );		
}

private function resolve( key:String ):Array
{
	var funcParts:Array = key.split(" ");
	var keyParts:Array = funcParts[0].split(".");
	var pp:Object = this.asset;
	var i:Int=0;
	if( keyParts[i] == "this" )
	{
	pp = this;
	i=1;
	}
	for( ; i<keyParts.length-1; ++i )
	{
	pp = pp[ keyParts[i] ];
	}
	var funcName:String = keyParts[i]; 
	if( funcParts.length>1 )
	{
	funcParts[0] = funcName;
	funcParts.unshift( pp );
	return funcParts;
	}
	return [pp, funcName];
}

private function processJML( jml:Object, result:Object ):Void
{
	if( jml is Array )
	{
	for each( var j:Object in jml )
	{
		this.processJML( j, result );
	}
	return;
	}
	
	for( var key:String in jml )
	{
	var val:Object = jml[key];
	var r:Array = this.resolve( key );
	var pp:Object = r[0];
	var nkey:String = r[1];
	var param:Object;
	
	if( nkey in pp || pp is Array || pp.constructor == Object )
	{
		if( val is String )
		{
		if( parent.translation && val.length > 2 && String(val).substr(0, 2) == "$$" && val in parent.translation )
		{
			val = parent.translation[val];
		}
		else if( val in this.jmlconstants )
		{
			val = this.jmlconstants[ val ];
		}
		
		pp[nkey] = val;
		result[ nkey ] = val;
		}
		else if( val is Boolean || val is int || val is Number || (val is Array && !(pp[nkey] is Function)) )
		{
		pp[nkey] = val;
		result[ nkey ] = val;
		}
		else if( val is Array )
		{
		pp[nkey].apply( pp, val );
		}
	}
	else if( key.substr(0, 5) == "@pipe" )
	{
		this.addPipe( String(val), function(...args):Void{} );
	}
	else if( key.indexOf(":") > -1 )
	{
		this.addChild( result[ key ] = parent.getControllerForType( key.split(":")[0], key, val ) );
	}
	else if( key == "@callback" )
	{
		if( val[0] is Array )
		{
		for each( call in val )
		{
			var oldCall1:String = call[1];
			if( call[1] in this.jmlfunctions )
			{
			call[1] = this.jmlfunctions[ call[1] ];
			}
			else
			{
			call[1] = parent[ call[1] ];
			}
			Object(this.asset).addCallback.apply( this.asset, call );
			call[1] = oldCall1;
		}
		}
		else
		{
		oldCall1 = val[1];
		if( val[1] in this.jmlfunctions )
		{
			val[1] = this.jmlfunctions[ val[1] ];
		}
		else
		{
			val[1] = parent[val[1]];
		}
		Object(this.asset).addCallback.apply( this.asset, val );
		val[1] = oldCall1;
		}
	}
	else if( val is Array && key in this.jmlfunctions )
	{
		this.jmlfunctions[ key ].apply( null, val );
	}
	else if( key == "@call" )
	{
		for each( var call:Object in val )
		{
		for( var ckey:String in call )
		{
			var res:Array = this.resolve( ckey );
			param = call[ckey];
			for( var cf:Int=res.length-1; cf>=1; --cf )
			{
			var prefix:String = res[cf].substr(0, 4);
			var postfix:String=res[cf].substr(4);
			switch( prefix )
			{
				case "new_":
				var nc:Class = ApplicationDomain.currentDomain.getDefinition(postfix) as Class;
				var nparam:Object = new nc();
				for( var nprop:String in param )
				{
					nparam[ nprop ] = param[ nprop ];
				}
				param = [nparam];
				break;
				case "set_":
				res[0][postfix] = param[0];
				break;
				case "":
				break;
				default:
				var tparam:Array = param.concat();
				for( var i:Int=0; i<tparam.length; ++i )
				{
					if( tparam[i] in this.jmlconstants )
					{
					tparam[i] = this.jmlconstants[ tparam[i] ];
					// trace( tparam[i], tparam[i] = this.jmlconstants[ tparam[i] ] );
					}
				}
				
				if( this.asset is Sprite && res[0] == Sprite(this.asset).graphics )
				{
					if( res[cf] == "beginGradientFill" && (tparam as Array).length >= 5 )
					{
					var mat:Matrix = new Matrix();
					mat.createGradientBox.apply( mat, tparam[4] );
					tparam[4] = mat;
					}
				}
				if( res[0] == this.asset && res[cf] in this.jmlfunctions )
				{
					this.jmlfunctions[ res[cf] ].apply( null, tparam );
				}
				else
				{
					res[0][ res[cf] ].apply( res[0], tparam );
				}
				break;
			}
			}
		}
		}
	}
	else
	{
		trace("Could not assign", val, "to", key);
	}
	}
}
}